#include "sdk.h"

#ifndef wxUSE_CHOICEDLG
#define wxUSE_CHOICEDLG 1
#endif

#include <wx/choicdlg.h>
#include <wx/filedlg.h>
#include <wx/filename.h>
#include <map>
#include "projectloader.h"
#include "projectlayoutloader.h"
#include "selecttargetdlg.h"
#include "filegroupsandmasks.h"
#include "filefilters.h"
#include "genericmultilinenotesdlg.h"
#include "compilercommandgenerator.h"
#include "M_Code_colourmanager.h"

M_Code_Project::M_Code_Project( const wxString& filename ) :
  m_CustomMakefile( false ),
  m_Globs(),
  m_FileArray( ProjectFile::CompareProjectFiles ),
  m_Loaded( false ),
  m_CurrentlyLoading( false ),
  m_PCHMode( pchSourceFile ),
  m_CurrentlyCompilingTarget( nullptr ),
  m_ExtendedObjectNamesGeneration( false ),
  m_AutoShowNotesOnLoad( false ),
  m_CheckForExternallyModifiedFiles( true ),
  m_pExtensionsElement( nullptr ) {
  SetCompilerID( CompilerFactory::GetDefaultCompilerID() );
  SetModified( false );
  wxString realFile = realpath( filename );
  m_Files.clear();
  m_FileArray.Clear();
  if( !realFile.IsEmpty() && ( wxFileExists( realFile ) || wxDirExists( realFile ) ) ) {
    m_Filename = realFile;
    m_BasePath = GetBasePath();
    Open();
  } else {
    SetModified( true );
    if( realFile.IsEmpty() ) {
      m_Filename = CreateUniqueFilename();
      m_Loaded   = SaveAs();
    } else {
      m_Filename = realFile;
      m_Loaded   = Save();
    }
    if( m_Loaded ) {
      wxFileName fname( m_Filename );
      m_Title = fname.GetName();
      m_BasePath = GetBasePath();
      m_CommonTopLevelPath = GetBasePath() + wxFileName::GetPathSeparator();
    }
  }
}

M_Code_Project::~M_Code_Project() {
  ClearAllProperties();
}

void M_Code_Project::NotifyPlugins( wxEventType type, const wxString& targetName, const wxString& oldTargetName ) {
  CodeBlocksEvent event( type );
  event.SetProject( this );
  event.SetBuildTargetName( targetName );
  event.SetOldBuildTargetName( oldTargetName );
  Manager::Get()->ProcessEvent( event );
}

void M_Code_Project::SetCompilerID( const wxString& id ) {
  CompileTargetBase::SetCompilerID( id );
  if( id != GetCompilerID() ) {
    for( unsigned int i = 0; i < m_Targets.GetCount(); ++i ) {
      ProjectBuildTarget* target = m_Targets[i];
      if( target ) {
        Compiler* compiler = CompilerFactory::GetCompiler( target->GetCompilerID() );
        if( !compiler ) {
          continue;
        }
        for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
          ProjectFile* pf = *it;
          wxFileName obj( pf->GetObjName() );
          if( ( FileTypeOf( pf->relativeFilename ) != ftResource )
              && ( obj.GetExt() == compiler->GetSwitches().objectExtension ) ) {
            obj.SetExt( compiler->GetSwitches().objectExtension );
            pf->SetObjName( obj.GetFullName() );
          }
        }
      }
    }
  }
}

bool M_Code_Project::GetModified() const {
  if( CompileOptionsBase::GetModified() ) {
    return true;
  }
  for( unsigned int i = 0; i < m_Targets.GetCount(); ++i ) {
    ProjectBuildTarget* target = m_Targets[i];
    if( target->GetModified() ) {
      return true;
    }
  }
  return false;
}

void M_Code_Project::SetModified( bool modified ) {
  CompileOptionsBase::SetModified( modified );
  for( unsigned int i = 0; i < m_Targets.GetCount(); ++i ) {
    ProjectBuildTarget* target = m_Targets[i];
    target->SetModified( modified );
  }
  if( !modified ) {
    m_LastSavedActiveTarget = m_ActiveTarget;
  }
}

void M_Code_Project::SetMakefileCustom( bool custom ) {
  if( m_CustomMakefile != custom ) {
    m_CustomMakefile = custom;
    SetModified( true );
  }
}

wxString M_Code_Project::CreateUniqueFilename() {
  const wxString prefix = _T( "Untitled" );
  wxString tmp;
  ProjectsArray* arr = Manager::Get()->GetProjectManager()->GetProjects();
  int projCount = arr->GetCount();
  int iter = 1;
  bool ok = false;
  tmp << prefix << F( _T( "%d" ), iter );
  while( !ok ) {
    tmp.Clear();
    tmp << prefix << F( _T( "%d" ), iter );
    ok = true;
    for( int i = 0; i < projCount; ++i ) {
      M_Code_Project* prj = arr->Item( i );
      wxFileName fname( prj->GetFilename() );
      if( fname.GetName().Matches( tmp ) ) {
        ok = false;
        break;
      }
    }
    if( ok ) {
      break;
    }
    ++iter;
  }
  return tmp << FileFilters::M_CODE_DOT_EXT;
}

void M_Code_Project::ClearAllProperties() {
  Delete( m_pExtensionsElement );
  for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
    delete( *it );
  }
  m_Files.clear();
  m_FileArray.Clear();
  m_CompilerOptions.Clear();
  m_LinkerOptions.Clear();
  m_IncludeDirs.Clear();
  m_LibDirs.Clear();
  while( m_Targets.GetCount() ) {
    ProjectBuildTarget* target = m_Targets[0];
    delete target;
    m_Targets.RemoveAt( 0 );
  }
  SetModified( true );
  NotifyPlugins( cbEVT_BUILDTARGET_SELECTED );
}

void M_Code_Project::Open() {
  m_Loaded = false;
  m_ProjectFilesMap.clear();
  Delete( m_pExtensionsElement );
  if( !wxFileName::FileExists( m_Filename ) && !wxFileName::DirExists( m_Filename ) ) {
    wxString msg;
    msg.Printf( _T( "项目 '%s' 不存在..." ), m_Filename.c_str() );
    msging( msg, _T( "错误" ), wxOK | wxCENTRE | wxICON_ERROR );
    return;
  }
  bool fileUpgraded = false;
  bool fileModified = false;
  wxFileName fname( m_Filename );
  FileType ft = FileTypeOf( m_Filename );
  if( ft == ftm_Code_Project ) {
    Manager::Get()->GetLogManager()->Log( _T( "打开 " ) + m_Filename );
    m_CurrentlyLoading = true;
    ProjectLoader loader( this );
    m_Loaded = loader.Open( m_Filename, &m_pExtensionsElement );
    fileUpgraded = loader.FileUpgraded();
    fileModified = loader.FileModified();
    m_CurrentlyLoading = false;
    if( m_Loaded ) {
      CalculateCommonTopLevelPath();
      if( !m_Targets.GetCount() ) {
        AddDefaultBuildTarget();
      }
      fileUpgraded = fileUpgraded && !Manager::IsBatchBuild();
      SetModified( ft != ftm_Code_Project || fileUpgraded || fileModified );
      if( fileUpgraded ) {
        InfoWindow::Tip( _T( "加载的项目文件是用旧版本的\n"
                             " 可以导入旧项目文件,\n"
                             "但将始终以当前格式保存。" ), m_Title, 12000, 2000 );
      }
      m_LastModified = fname.GetModificationTime();
    }
  }
}

void M_Code_Project::CalculateCommonTopLevelPath() {
  const wxString sep = wxFileName::GetPathSeparator();
  wxFileName base = GetBasePath() + sep;
  wxString vol = base.GetVolume();
  bool prjHasUNCName  = base.GetFullPath().StartsWith( _T( "\\\\" ) );
  Manager::Get()->GetLogManager()->Log( _T( "项目根路径: " ) + base.GetFullPath() );
  #ifdef ctlp_measuring
  wxStopWatch sw;
  #endif
  for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
    ProjectFile* f = ( *it );
    if( !f ) {
      continue;
    }
    if( !vol.IsSameAs( f->file.GetVolume() ) ) {
      continue;
    }
    bool fileHasUNCName = f->file.GetFullPath().StartsWith( _T( "\\\\" ) );
    if( ( !prjHasUNCName &&  fileHasUNCName ) || ( prjHasUNCName && !fileHasUNCName ) ) {
      continue;
    }
    wxString tmp     = f->relativeFilename;
    wxString tmpbase = m_BasePath;
    size_t pos = 0;
    while( ( pos < tmp.Length() ) && ( ( tmp.GetChar( pos ) == _T( '.' ) )
                                       || ( tmp.GetChar( pos ) == _T( '/' ) ) || ( tmp.GetChar( pos ) == _T( '\\' ) ) ) ) {
      ++pos;
    }
    if( ( pos > 0 ) && ( pos < tmp.Length() ) ) {
      tmpbase << sep << tmp.Left( pos ) << sep;
    }
    wxFileName tmpbaseF( tmpbase );
    tmpbaseF.Normalize( wxPATH_NORM_DOTS );
    if( ( tmpbaseF.GetDirCount() < base.GetDirCount() )
        && ( base.GetPath().StartsWith( tmpbaseF.GetPath() ) ) ) {
      base = tmpbaseF;
    }
  }
  m_CommonTopLevelPath = base.GetFullPath();
  Manager::Get()->GetLogManager()->DebugLog( _T( "项目公共根路径: " ) + m_CommonTopLevelPath );
  const wxString &projectBasePath = GetBasePath();
  for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
    ProjectFile* f = ( *it );
    if( !f ) {
      continue;
    }
    wxString fileName = f->file.GetFullPath();
    bool fileHasUNCName = fileName.StartsWith( _T( "\\\\" ) );
    if( ( prjHasUNCName && fileHasUNCName ) || ( !prjHasUNCName && !fileHasUNCName && vol.IsSameAs( f->file.GetVolume() ) ) ) {
      wxFileName relFileCTLP( f->file );
      relFileCTLP.MakeRelativeTo( m_CommonTopLevelPath );
      wxFileName relFileBase( f->file );
      relFileBase.MakeRelativeTo( projectBasePath );
      f->relativeToCommonTopLevelPath = relFileCTLP.GetFullPath();
      f->relativeFilename = relFileBase.GetFullPath();
    } else {
      f->relativeToCommonTopLevelPath = fileName;
      f->relativeFilename = fileName;
    }
    f->SetObjName( f->relativeToCommonTopLevelPath );
  }
}

wxString M_Code_Project::GetCommonTopLevelPath() const {  // 项目根路径
  return m_CommonTopLevelPath;
}

void M_Code_Project::Touch() {
  m_LastModified = wxDateTime::Now();
}

bool M_Code_Project::SaveAs() {
  wxFileName fname;
  fname.Assign( m_Filename );
  wxFileDialog dlg( Manager::Get()->GetAppWindow(), _T( "保存文件" ), fname.GetPath(),
                    fname.GetFullName(), FileFilters::GetFilterString( FileFilters::M_CODE_DOT_EXT ),
                    wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() != wxID_OK ) {
    return false;
  }
  wxFileName newName( dlg.GetPath() );
  bool pathChanged = !newName.GetPath().IsSameAs( fname.GetPath() );
  m_Filename = newName.GetFullPath();
  fname.Assign( m_Filename );
  if( !fname.GetExt().Matches( FileFilters::M_CODE_EXT ) ) {
    fname.Assign( m_Filename + FileFilters::M_CODE_DOT_EXT );
  }
  if( !m_Loaded ) {
    AddDefaultBuildTarget();
  }
  if( pathChanged ) {
    CalculateCommonTopLevelPath();
  }
  ProjectLoader loader( this );
  if( loader.Save( m_Filename, m_pExtensionsElement ) ) {
    fname = m_Filename;
    m_LastModified = fname.GetModificationTime();
    NotifyPlugins( cbEVT_PROJECT_SAVE );
    return true;
  }
  msging( _T( "无法保存项目 " ) + m_Filename + _T( "\n（可能文件有写保护？）" ), _T( "警告" ), wxICON_WARNING );
  return false;
}

bool M_Code_Project::Save() {
  if( m_Filename.IsEmpty() ) {
    return SaveAs();
  }
  ProjectLoader loader( this );
  if( loader.Save( m_Filename, m_pExtensionsElement ) ) {
    wxFileName fname( m_Filename );
    m_LastModified = fname.GetModificationTime();
    NotifyPlugins( cbEVT_PROJECT_SAVE );
    return true;
  }
  msging( _T( "无法保存项目 " ) + m_Filename + _T( "\n（可能文件有写保护？）" ), _T( "警告" ), wxICON_WARNING );
  return false;
}

bool M_Code_Project::SaveLayout() {
  if( m_Filename.IsEmpty() ) {
    return false;
  }
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/enable_project_layout" ), true ) == false ) {
    return true;
  }
  wxFileName fname( m_Filename );
  fname.SetExt( _T( "layout" ) );
  ProjectLayoutLoader loader( this );
  return loader.Save( fname.GetFullPath() );
}

bool M_Code_Project::LoadLayout() {
  if( m_Filename.IsEmpty() ) {
    return false;
  }
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/enable_project_layout" ), true ) == false ) {
    return true;
  }
  int openmode = Manager::Get()->GetConfigManager( _T( "project_manager" ) )->ReadInt( _T( "/open_files" ), ( long int )1 );
  if( openmode == 2 ) {
    return true;
  }
  Manager::Get()->GetEditorManager()->HideNotebook();
  bool result = false;
  if( openmode == 0 ) {
    FilesList::iterator it = m_Files.begin();
    while( it != m_Files.end() ) {
      ProjectFile* f = *it++;
      Manager::Get()->GetEditorManager()->Open( f->file.GetFullPath(), 0, f );
    }
    result = true;
  } else if( openmode == 1 ) {
    wxFileName fname( m_Filename );
    fname.SetExt( _T( "layout" ) );
    ProjectLayoutLoader loader( this );
    if( loader.Open( fname.GetFullPath() ) ) {
      typedef std::map<int, ProjectFile*> open_files_map;
      open_files_map open_files;
      FilesList::iterator it = m_Files.begin();
      while( it != m_Files.end() ) {
        ProjectFile* f = *it++;
        if( f->editorOpen && wxFileExists( f->file.GetFullPath() ) ) {
          open_files[f->editorTabPos] = f;
        } else {
          f->editorOpen = false;
        }
      }
      std::vector<LoaderBase*> filesInMemory;
      for( open_files_map::iterator ofm_it = open_files.begin(); ofm_it != open_files.end(); ++ofm_it ) {
        filesInMemory.push_back( Manager::Get()->GetFileManager()->Load( ( *ofm_it ).second->file.GetFullPath() ) );
      }
      size_t i = 0;
      for( open_files_map::iterator ofm_it = open_files.begin(); ofm_it != open_files.end(); ++ofm_it ) {
        M_Code_Editor* ed = Manager::Get()->GetEditorManager()->Open( filesInMemory[i], ( *ofm_it ).second->file.GetFullPath(), 0, ( *ofm_it ).second );
        if( ed ) {
          ed->SetProjectFile( ( *ofm_it ).second );
        }
        ++i;
      }
      ProjectFile* f = loader.GetTopProjectFile();
      if( f ) {
        Manager::Get()->GetLogManager()->Log( f->file.GetFullPath() + _T( " ---- 文件激活编辑器" ) );
        EditorBase* eb = Manager::Get()->GetEditorManager()->Open( f->file.GetFullPath() );
        if( eb ) {
          eb->Activate();
        }
      }
      loader.LoadNotebookLayout();
    }
    result = true;
  }
  Manager::Get()->GetEditorManager()->ShowNotebook();
  return result;
}

void M_Code_Project::BeginAddFiles() {
  CodeBlocksEvent event( cbEVT_PROJECT_BEGIN_ADD_FILES );
  event.SetProject( this );
  Manager::Get()->ProcessEvent( event );
}

void M_Code_Project::EndAddFiles() {
  CodeBlocksEvent event( cbEVT_PROJECT_END_ADD_FILES );
  event.SetProject( this );
  Manager::Get()->ProcessEvent( event );
}

void M_Code_Project::BeginRemoveFiles() {
  CodeBlocksEvent event( cbEVT_PROJECT_BEGIN_REMOVE_FILES );
  event.SetProject( this );
  Manager::Get()->ProcessEvent( event );
}

void M_Code_Project::EndRemoveFiles() {
  CodeBlocksEvent event( cbEVT_PROJECT_END_REMOVE_FILES );
  event.SetProject( this );
  Manager::Get()->ProcessEvent( event );
}

ProjectFile* M_Code_Project::AddFile( const wxString& targetName, const wxString& filename, bool compile, bool link, unsigned short int weight ) {
  int idx = IndexOfBuildTargetName( targetName );
  return AddFile( idx, filename, compile, link, weight );
}

ProjectFile* M_Code_Project::AddFile( int targetIndex, const wxString& filename, bool compile, bool link, M_Code_unused unsigned short int weight ) {
  ProjectFile* pf = m_ProjectFilesMap[UnixFilename( filename )];
  if( pf ) {
    return pf;
  }
  pf = new ProjectFile( this );
  bool localCompile, localLink;
  wxFileName fname( filename );
  const wxString &ext = fname.GetExt();
  if( ext.IsSameAs( FileFilters::C_EXT, false ) ) {
    pf->compilerVar = _T( "CC" );
  } else {
    pf->compilerVar = _T( "CPP" );
  }
  if( !m_Targets.GetCount() ) {
    AddDefaultBuildTarget();
    if( !m_Targets.GetCount() ) {
      delete pf;
      return nullptr;
    }
  }
  const FileType ft = FileTypeOf( filename );
  const bool isResource = ( ft == ftResource );
  std::map<Compiler*, const CompilerTool*> GenFilesHackMap;
  if( targetIndex < 0 || targetIndex >= ( int )m_Targets.GetCount() ) {
    Compiler* c = CompilerFactory::GetCompiler( GetCompilerID() );
    if( c ) {
      const CompilerTool* t = c->GetCompilerTool( isResource ? ctCompileResourceCmd : ctCompileObjectCmd, fname.GetExt() );
      if( t && t->generatedFiles.GetCount() ) {
        GenFilesHackMap[c] = t;
      }
    }
    for( unsigned int i = 0; i < m_Targets.GetCount(); ++i ) {
      c = CompilerFactory::GetCompiler( m_Targets[i]->GetCompilerID() );
      if( GenFilesHackMap.find( c ) != GenFilesHackMap.end() ) {
        continue;
      }
      if( c ) {
        const CompilerTool* t = c->GetCompilerTool( isResource ? ctCompileResourceCmd : ctCompileObjectCmd, fname.GetExt() );
        if( t && t->generatedFiles.GetCount() ) {
          GenFilesHackMap[c] = t;
        }
      }
    }
  } else {
    Compiler* c = CompilerFactory::GetCompiler( m_Targets[targetIndex]->GetCompilerID() );
    if( c ) {
      const CompilerTool* t = c->GetCompilerTool( isResource ? ctCompileResourceCmd : ctCompileObjectCmd, fname.GetExt() );
      if( t && t->generatedFiles.GetCount() ) {
        GenFilesHackMap[c] = t;
      }
    }
  }
  if( targetIndex >= 0 && targetIndex < ( int )m_Targets.GetCount() ) {
    pf->AddBuildTarget( m_Targets[targetIndex]->GetTitle() );
  }
  localCompile = compile && ( ft == ftSource || ft == ftResource || !GenFilesHackMap.empty() );
  localLink = link && ( ft == ftSource || ft == ftResource || ft == ftObject || ft == ftResourceBin || ft == ftStaticLib );
  pf->compile = localCompile;
  pf->link = localLink;
  wxString local_filename = filename;
  const wxString &projectBasePath = GetBasePath();
  if( fname.IsAbsolute() ) {
    fname.MakeRelativeTo( projectBasePath );
    local_filename = fname.GetFullPath();
  }
  fname.Assign( projectBasePath + wxFILE_SEP_PATH + local_filename );
  fname.Normalize( wxPATH_NORM_DOTS | wxPATH_NORM_TILDE, projectBasePath );
  const wxString &fullFilename = realpath( fname.GetFullPath() );
  pf->file = fullFilename;
  pf->relativeFilename  = UnixFilename( local_filename );
  ProjectFile* existing = GetFileByFilename( pf->relativeFilename, true, true );
  if( existing == pf ) {
    delete pf;
    if( targetIndex >= 0 && targetIndex < ( int )m_Targets.GetCount() ) {
      existing->AddBuildTarget( m_Targets[targetIndex]->GetTitle() );
    }
    return existing;
  }
  m_Files.insert( pf );
  if( !m_CurrentlyLoading ) {
    if( m_FileArray.GetCount() > 0 ) {
      m_FileArray.Add( pf );
    }
    if( !fullFilename.StartsWith( m_CommonTopLevelPath ) ) {
      CalculateCommonTopLevelPath();
    } else {
      pf->relativeToCommonTopLevelPath = fullFilename.Right( fullFilename.Length() - m_CommonTopLevelPath.Length() );
    }
  }
  SetModified( true );
  m_ProjectFilesMap[pf->relativeFilename] = pf;
  if( !wxFileExists( fullFilename ) ) {
    pf->SetFileState( fvsMissing );
  } else if( !wxFile::Access( fullFilename.c_str(), wxFile::write ) ) {
    pf->SetFileState( fvsReadOnly );
  }
  if( !GenFilesHackMap.empty() ) {
    wxFileName tmp = pf->file;
    for( std::map<Compiler*, const CompilerTool*>::const_iterator it = GenFilesHackMap.begin(); it != GenFilesHackMap.end(); ++it ) {
      const CompilerTool* tool = it->second;
      for( size_t i = 0; i < tool->generatedFiles.GetCount(); ++i ) {
        tmp.SetFullName( tool->generatedFiles[i] );
        wxString tmps = tmp.GetFullPath();
        tmps.Replace( _T( "$file_basename" ), pf->file.GetName() );
        tmps.Replace( _T( "$file_name" ), pf->file.GetName() );
        tmps.Replace( _T( "$file_dir" ), pf->file.GetPath() );
        tmps.Replace( _T( "$file_ext" ), pf->file.GetExt() );
        tmps.Replace( _T( "$file" ), pf->file.GetFullName() );
        Manager::Get()->GetMacrosManager()->ReplaceMacros( tmps );
        ProjectFile* pfile = AddFile( targetIndex, UnixFilename( tmps ) );
        if( !pfile ) {
          Manager::Get()->GetLogManager()->DebugLog( _T( "无法添加自动生成的文件" ) + tmps );
        } else {
          pf->generatedFiles.push_back( pfile );
          pfile->SetAutoGeneratedBy( pf );
        }
      }
    }
  }
  return pf;
}

bool M_Code_Project::RemoveFile( ProjectFile* pf ) {
  if( !pf ) {
    return false;
  }
  m_ProjectFilesMap.erase( UnixFilename( pf->relativeFilename ) );
  Manager::Get()->GetEditorManager()->Close( pf->file.GetFullPath() );
  FilesList::iterator it = m_Files.find( pf );
  if( it == m_Files.end() ) {
    Manager::Get()->GetLogManager()->DebugLog( _T( "找不到的节点 ProjectFile* !" ) );
  } else {
    if( !m_FileArray.IsEmpty() ) {
      m_FileArray.Remove( *it );
    }
    m_Files.erase( it );
  }
  for( unsigned int i = 0; i < m_Targets.GetCount(); ++i ) {
    if( ProjectBuildTarget* target = m_Targets[i] ) {
      target->RemoveFile( pf );
    }
  }
  if( pf->AutoGeneratedBy() ) {
    ProjectFilesVector::iterator it = std::find( pf->AutoGeneratedBy()->generatedFiles.begin(),
                                      pf->AutoGeneratedBy()->generatedFiles.end(), pf );
    pf->AutoGeneratedBy()->generatedFiles.erase( it );
  }
  while( pf->generatedFiles.size() ) {
    RemoveFile( pf->generatedFiles[0] );
  }
  pf->generatedFiles.clear();
  delete pf;
  SetModified( true );
  return true;
}

const wxArrayString& M_Code_Project::GetVirtualFolders() const {
  return m_VirtualFolders;
}

bool M_Code_Project::AppendUniqueVirtualFolder( const wxString &folder ) {
  if( m_VirtualFolders.Index( folder ) == wxNOT_FOUND ) {
    m_VirtualFolders.push_back( folder );
    return true;
  } else {
    return false;
  }
}

void M_Code_Project::RemoveVirtualFolders( const wxString &folder ) {
  for( int i = ( int )m_VirtualFolders.GetCount() - 1; i >= 0; --i ) {
    if( m_VirtualFolders[i].StartsWith( folder ) ) {
      m_VirtualFolders.RemoveAt( i );
    }
  }
  for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
    ProjectFile* f = *it;
    if( f && !f->virtual_path.IsEmpty() ) {
      if( f->virtual_path.StartsWith( folder ) ) {
        f->virtual_path.Clear();
      }
    }
  }
  SetModified( true );
}

void M_Code_Project::ReplaceVirtualFolder( const wxString &oldFolder, const wxString &newFolder ) {
  int idx = m_VirtualFolders.Index( oldFolder );
  if( idx != wxNOT_FOUND ) {
    m_VirtualFolders[idx] = newFolder;
  } else {
    m_VirtualFolders.Add( newFolder );
  }
  for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
    ProjectFile* f = *it;
    if( f && !f->virtual_path.IsEmpty() ) {
      if( f->virtual_path.StartsWith( oldFolder ) ) {
        f->virtual_path.Replace( oldFolder, newFolder );
      }
    }
  }
  SetModified( true );
}


void M_Code_Project::SetVirtualFolders( const wxArrayString& folders ) {
  m_VirtualFolders = folders;
  for( size_t i = 0; i < m_VirtualFolders.GetCount(); ++i ) {
    m_VirtualFolders[i].Replace( _T( "/" ), wxString( wxFILE_SEP_PATH ) );
    m_VirtualFolders[i].Replace( _T( "\\" ), wxString( wxFILE_SEP_PATH ) );
  }
}

void M_Code_Project::SaveTreeState( wxTreeCtrl* tree ) {
  ::SaveTreeState( tree, m_ProjectNode, m_ExpandedNodes, m_SelectedNodes );
}

void M_Code_Project::RestoreTreeState( wxTreeCtrl* tree ) {
  ::RestoreTreeState( tree, m_ProjectNode, m_ExpandedNodes, m_SelectedNodes );
}

const wxString& M_Code_Project::GetMakefile() {
  if( !m_Makefile.IsEmpty() ) {
    return m_Makefile;
  }
  wxFileName makefile( m_Makefile );
  makefile.Assign( m_Filename );
  makefile.SetName( _T( "Makefile" ) );
  makefile.SetExt( _T( "" ) );
  makefile.MakeRelativeTo( GetBasePath() );
  m_Makefile = makefile.GetFullPath();
  return m_Makefile;
}

void M_Code_Project::SetMakefileExecutionDir( const wxString& dir ) {
  if( m_MakefileExecutionDir != dir ) {
    m_MakefileExecutionDir = dir;
    SetModified( true );
  }
}

wxString M_Code_Project::GetMakefileExecutionDir() {
  if( m_MakefileExecutionDir.IsEmpty() ) {
    wxFileName execution_dir( GetBasePath() );
    m_MakefileExecutionDir = execution_dir.GetFullPath();
  }
  return m_MakefileExecutionDir;
}

wxString M_Code_Project::GetExecutionDir() {
  if( !m_CustomMakefile ) {
    return GetBasePath();
  }
  return GetMakefileExecutionDir();
}

ProjectFile* M_Code_Project::GetFile( int index ) {
  if( m_FileArray.GetCount() == 0 ) {
    for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
      if( !*it ) {
        continue;
      }
      m_FileArray.Add( ( ProjectFile* )*it );
    }
  }
  if( index < 0 || index >= static_cast<int>( m_Files.size() ) ) {
    return NULL;
  }
  return m_FileArray.Item( index );
}

ProjectFile* M_Code_Project::GetFileByFilename( const wxString& filename, bool isRelative, bool isUnixFilename ) {
  wxString tmp = filename;
  if( !isRelative ) {
    wxFileName fname( realpath( filename ) );
    fname.MakeRelativeTo( GetBasePath() );
    tmp = fname.GetFullPath();
  } else {
    if( tmp.StartsWith( _T( ".\\" ) ) || tmp.StartsWith( _T( "./" ) ) ) {
      tmp.Remove( 0, 2 );
    }
  }
  if( isUnixFilename ) {
    return m_ProjectFilesMap[tmp];
  }
  return m_ProjectFilesMap[UnixFilename( tmp )];
}

bool M_Code_Project::QueryCloseAllFiles() {
  FilesList::iterator it = m_Files.begin();
  while( it != m_Files.end() ) {
    ProjectFile* f = *it++;
    M_Code_Editor* ed = Manager::Get()->GetEditorManager()->IsBuiltinOpen( f->file.GetFullPath() );
    if( ed && ed->GetModified() ) {
      if( !Manager::Get()->GetEditorManager()->QueryClose( ed ) ) {
        return false;
      }
    }
  }
  return true;
}

bool M_Code_Project::CloseAllFiles( bool dontsave ) {
  if( !dontsave && !QueryCloseAllFiles() ) {
    return false;
  }
  Manager::Get()->GetEditorManager()->HideNotebook();
  for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
    ProjectFile* f = *it;
    if( f ) {
      Manager::Get()->GetEditorManager()->Close( f->file.GetFullPath(), true );
    }
    delete f;
  }
  m_FileArray.Clear();
  m_Files.clear();
  Manager::Get()->GetEditorManager()->ShowNotebook();
  return true;
}

bool M_Code_Project::SaveAllFiles() {
  int count = m_Files.size();
  FilesList::iterator it = m_Files.begin();
  while( it != m_Files.end() ) {
    ProjectFile* f = *it++;
    if( Manager::Get()->GetEditorManager()->Save( f->file.GetFullPath() ) ) {
      --count;
    }
  }
  return count == 0;
}

int M_Code_Project::SelectTarget( int initial, bool evenIfOne ) {
  if( !evenIfOne && GetBuildTargetsCount() == 1 ) {
    return 0;
  }
  SelectTargetDlg dlg( nullptr, this, initial );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    return dlg.GetSelection();
  }
  return -1;
}


ProjectBuildTarget* M_Code_Project::AddDefaultBuildTarget() {
  return AddBuildTarget( _T( "default" ) );
}

ProjectBuildTarget* M_Code_Project::AddBuildTarget( const wxString& targetName ) {
  if( GetBuildTarget( targetName ) ) {
    return nullptr;
  }
  ProjectBuildTarget* target = new ProjectBuildTarget( this );
  target->m_Filename = m_Filename;
  target->SetTitle( targetName );
  target->SetCompilerID( GetCompilerID() );
  target->SetOutputFilename( targetName );
  target->SetWorkingDir( _T( "dev" ) );
  target->SetObjectOutput( _T( "/tmp/" ) + targetName );
  target->SetDepsOutput( _T( ".deps" ) );
  m_Targets.Add( target );
  if( HasVirtualBuildTarget( targetName ) ) {
    RemoveVirtualBuildTarget( targetName );
    Manager::Get()->GetLogManager()->LogWarning( F( _T( "已删除现有虚拟目标 '%s' 因为真正的目标是用相同的名称添加的" ), targetName.wx_str() ) );
  }
  SetModified( true );
  NotifyPlugins( cbEVT_BUILDTARGET_ADDED, targetName );
  NotifyPlugins( cbEVT_PROJECT_TARGETS_MODIFIED );
  return target;
}

bool M_Code_Project::RenameBuildTarget( int index, const wxString& targetName ) {
  ProjectBuildTarget* target = GetBuildTarget( index );
  if( target ) {
    wxString oldTargetName = target->GetTitle();
    for( VirtualBuildTargetsMap::iterator it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it ) {
      wxArrayString& tgts = it->second;
      index = tgts.Index( target->GetTitle() );
      if( index != -1 ) {
        tgts[index] = targetName;
      }
    }
    for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
      ProjectFile* pf = *it;
      pf->RenameBuildTarget( target->GetTitle(), targetName );
    }
    target->SetTitle( targetName );
    SetModified( true );
    NotifyPlugins( cbEVT_BUILDTARGET_RENAMED, targetName, oldTargetName );
    NotifyPlugins( cbEVT_PROJECT_TARGETS_MODIFIED );
    return true;
  }
  return false;
}

bool M_Code_Project::RenameBuildTarget( const wxString& oldTargetName, const wxString& newTargetName ) {
  return RenameBuildTarget( IndexOfBuildTargetName( oldTargetName ), newTargetName );
}

ProjectBuildTarget* M_Code_Project::DuplicateBuildTarget( int index, const wxString& newName ) {
  ProjectBuildTarget* newTarget = nullptr;
  ProjectBuildTarget* target = GetBuildTarget( index );
  if( target ) {
    newTarget = new ProjectBuildTarget( *target );
    wxString newTargetName = !newName.IsEmpty() ? newName : ( _T( "Copy of " ) + target->GetTitle() );
    newTarget->SetTitle( newTargetName );
    for( FilesList::iterator it = newTarget->GetFilesList().begin(); it != newTarget->GetFilesList().end(); ++it ) {
      ProjectFile* pf = *it;
      pf->AddBuildTarget( newTargetName );
    }
    SetModified( true );
    m_Targets.Add( newTarget );
    NotifyPlugins( cbEVT_BUILDTARGET_ADDED, newName, target->GetTitle() );
    NotifyPlugins( cbEVT_PROJECT_TARGETS_MODIFIED );
  }
  return newTarget;
}

ProjectBuildTarget* M_Code_Project::DuplicateBuildTarget( const wxString& targetName, const wxString& newName ) {
  return DuplicateBuildTarget( IndexOfBuildTargetName( targetName ), newName );
}

bool M_Code_Project::ExportTargetAsProject( int index ) {
  ProjectBuildTarget* target = GetBuildTarget( index );
  if( !target ) {
    return false;
  }
  return ExportTargetAsProject( target->GetTitle() );
}

bool M_Code_Project::ExportTargetAsProject( const wxString& targetName ) {
  ProjectBuildTarget* target = GetBuildTarget( targetName );
  if( !target ) {
    return false;
  }
  wxString newName = cbGetTextFromUser( _T( "请输入新项目的名称（无路径，无扩展名）." ), _T( "将目标导出为新项目" ), target->GetTitle() );
  if( newName.IsEmpty() ) {
    return false;
  }
  wxFileName fname( GetFilename() );
  fname.SetName( newName );
  Save();
  bool alreadyModified = GetModified();
  wxString oldTitle = GetTitle();
  SetTitle( targetName );
  ProjectLoader loader( this );
  bool ret = loader.ExportTargetAsProject( fname.GetFullPath(), target->GetTitle(), m_pExtensionsElement );
  SetTitle( oldTitle );
  if( !alreadyModified ) {
    SetModified( false );
  }
  return ret;
}

bool M_Code_Project::RemoveBuildTarget( int index ) {
  ProjectBuildTarget* target = GetBuildTarget( index );
  if( target ) {
    wxString oldTargetName = target->GetTitle();
    for( VirtualBuildTargetsMap::iterator it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it ) {
      wxArrayString& tgts = it->second;
      int virt_idx = tgts.Index( target->GetTitle() );
      if( virt_idx != -1 ) {
        tgts.RemoveAt( virt_idx );
      }
    }
    for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
      ProjectFile* pf = *it;
      pf->RemoveBuildTarget( target->GetTitle() );
    }
    NotifyPlugins( cbEVT_BUILDTARGET_REMOVED, oldTargetName );
    delete target;
    m_Targets.RemoveAt( index );
    SetModified( true );
    NotifyPlugins( cbEVT_PROJECT_TARGETS_MODIFIED );
    return true;
  }
  return false;
}

bool M_Code_Project::RemoveBuildTarget( const wxString& targetName ) {
  return RemoveBuildTarget( IndexOfBuildTargetName( targetName ) );
}

int M_Code_Project::IndexOfBuildTargetName( const wxString& targetName ) const {
  for( unsigned int i = 0; i < m_Targets.GetCount(); ++i ) {
    ProjectBuildTarget* target = m_Targets[i];
    if( target->GetTitle().Matches( targetName ) ) {
      return i;
    }
  }
  return -1;
}

bool M_Code_Project::BuildTargetValid( const wxString& name, bool virtuals_too ) const {
  if( virtuals_too && HasVirtualBuildTarget( name ) ) {
    return true;
  } else if( IndexOfBuildTargetName( name ) != -1 ) {
    return true;
  }
  return false;
}

wxString M_Code_Project::GetFirstValidBuildTargetName( bool virtuals_too ) const {
  if( virtuals_too && !m_VirtualTargets.empty() ) {
    return m_VirtualTargets.begin()->first;
  } else if( m_Targets.GetCount() && m_Targets[0] ) {
    return m_Targets[0]->GetTitle();
  }
  return wxEmptyString;
}

bool M_Code_Project::SetActiveBuildTarget( const wxString& name ) {
  if( name == m_ActiveTarget ) {
    return true;
  }
  wxString oldActiveTarget = m_ActiveTarget;
  m_ActiveTarget = name;
  bool valid = BuildTargetValid( name );
  if( !valid ) {
    m_ActiveTarget = GetFirstValidBuildTargetName();
  }
  NotifyPlugins( cbEVT_BUILDTARGET_SELECTED, m_ActiveTarget, oldActiveTarget );
  return valid;
}

const wxString& M_Code_Project::GetActiveBuildTarget() const {
  return m_ActiveTarget;
}

void M_Code_Project::SetDefaultExecuteTarget( const wxString& name ) {
  if( name == m_DefaultExecuteTarget ) {
    return;
  }
  m_DefaultExecuteTarget = name;
  SetModified( true );
}

const wxString& M_Code_Project::GetDefaultExecuteTarget() const {
  return m_DefaultExecuteTarget;
}

ProjectBuildTarget* M_Code_Project::GetBuildTarget( int index ) {
  if( index >= 0 && index < ( int )m_Targets.GetCount() ) {
    return m_Targets[index];
  }
  return nullptr;
}

ProjectBuildTarget* M_Code_Project::GetBuildTarget( const wxString& targetName ) {
  int idx = IndexOfBuildTargetName( targetName );
  return GetBuildTarget( idx );
}

void M_Code_Project::ReOrderTargets( const wxArrayString& nameOrder ) {
  LogManager* msgMan = Manager::Get()->GetLogManager();
  if( nameOrder.GetCount() != m_Targets.GetCount() ) {
    msgMan->DebugLog( F( _T( "M_Code_Project::ReOrderTargets() : Count does not match (%lu sent, %lu had)..." ),
                         static_cast<unsigned long>( nameOrder.GetCount() ),
                         static_cast<unsigned long>( m_Targets.GetCount() ) ) );
    return;
  }
  for( unsigned int i = 0; i < nameOrder.GetCount(); ++i ) {
    ProjectBuildTarget* target = GetBuildTarget( nameOrder[i] );
    if( !target ) {
      msgMan->DebugLog( F( _T( "M_Code_Project::ReOrderTargets() : Target \"%s\" not found..." ), nameOrder[i].wx_str() ) );
      break;
    }
    m_Targets.Remove( target );
    m_Targets.Insert( target, i );
    VirtualBuildTargetsMap::iterator it;
    for( it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it ) {
      wxArrayString& vt = it->second;
      if( vt.Index( nameOrder[i] ) != wxNOT_FOUND ) {
        vt.Remove( nameOrder[i] );
        vt.Insert( nameOrder[i], ( vt.Count() <= i ) ? vt.Count() - 1 : i );
      }
    }
  }
  SetModified( true );
}

void M_Code_Project::SetCurrentlyCompilingTarget( ProjectBuildTarget* bt ) {
  m_CurrentlyCompilingTarget = bt;
}

bool M_Code_Project::DefineVirtualBuildTarget( const wxString& alias, const wxArrayString& targets ) {
  if( targets.GetCount() == 0 ) {
    Manager::Get()->GetLogManager()->LogWarning( F( _T( "无法定义虚拟生成目标 '%s': 生成目标组为空!" ), alias.wx_str() ) );
    return false;
  }
  ProjectBuildTarget* existing = GetBuildTarget( alias );
  if( existing ) {
    Manager::Get()->GetLogManager()->LogWarning( F( _T( "无法定义虚拟生成目标 '%s': 存在具有该名称的实际生成目标!" ), alias.wx_str() ) );
    return false;
  }
  m_VirtualTargets[alias] = targets;
  SetModified( true );
  NotifyPlugins( cbEVT_PROJECT_TARGETS_MODIFIED );
  return true;
}

bool M_Code_Project::HasVirtualBuildTarget( const wxString& alias ) const {
  return m_VirtualTargets.find( alias ) != m_VirtualTargets.end();
}

bool M_Code_Project::RemoveVirtualBuildTarget( const wxString& alias ) {
  VirtualBuildTargetsMap::iterator it = m_VirtualTargets.find( alias );
  if( it == m_VirtualTargets.end() ) {
    return false;
  }
  m_VirtualTargets.erase( it );
  SetModified( true );
  NotifyPlugins( cbEVT_PROJECT_TARGETS_MODIFIED );
  return true;
}

wxArrayString M_Code_Project::GetVirtualBuildTargets() const {
  wxArrayString result;
  for( VirtualBuildTargetsMap::const_iterator it = m_VirtualTargets.begin(); it != m_VirtualTargets.end(); ++it ) {
    result.Add( it->first );
  }
  return result;
}

const wxArrayString& M_Code_Project::GetVirtualBuildTargetGroup( const wxString& alias ) const {
  static wxArrayString resultIfError;
  VirtualBuildTargetsMap::const_iterator it = m_VirtualTargets.find( alias );
  if( it == m_VirtualTargets.end() ) {
    return resultIfError;
  }
  return it->second;
}

wxArrayString M_Code_Project::GetExpandedVirtualBuildTargetGroup( const wxString& alias ) const {
  wxArrayString result;
  VirtualBuildTargetsMap::const_iterator it = m_VirtualTargets.find( alias );
  if( it == m_VirtualTargets.end() ) {
    return result;
  }
  ExpandVirtualBuildTargetGroup( alias, result );
  return result;
}

bool M_Code_Project::CanAddToVirtualBuildTarget( const wxString& alias, const wxString& target ) {
  if( !HasVirtualBuildTarget( alias ) ) {
    return false;
  }
  if( !HasVirtualBuildTarget( target ) ) {
    return true;
  }
  const wxArrayString& group = GetVirtualBuildTargetGroup( target );
  if( group.Index( alias ) != wxNOT_FOUND ) {
    return false;
  }
  for( size_t i = 0; i < group.GetCount(); ++i ) {
    if( HasVirtualBuildTarget( group[i] ) ) {
      if( !CanAddToVirtualBuildTarget( group[i], alias ) ) {
        return false;
      }
    }
  }
  return true;
}

void M_Code_Project::ExpandVirtualBuildTargetGroup( const wxString& alias, wxArrayString& result ) const {
  const wxArrayString& group = GetVirtualBuildTargetGroup( alias );
  for( size_t i = 0; i < group.GetCount(); ++i ) {
    if( IndexOfBuildTargetName( group[i] ) != -1 ) {
      if( result.Index( group[i] ) == wxNOT_FOUND ) {
        result.Add( group[i] );
      }
    } else
    { ExpandVirtualBuildTargetGroup( group[i], result ); }
  }
}

void M_Code_Project::SetExtendedObjectNamesGeneration( bool ext ) {
  bool changed = m_ExtendedObjectNamesGeneration != ext;
  m_ExtendedObjectNamesGeneration = ext;
  if( changed ) {
    for( FilesList::iterator it = m_Files.begin(); it != m_Files.end(); ++it ) {
      ProjectFile* f = *it;
      f->SetObjName( f->relativeToCommonTopLevelPath );
      f->UpdateFileDetails();
    }
    SetModified( true );
  }
}

bool M_Code_Project::GetExtendedObjectNamesGeneration() const {
  return m_ExtendedObjectNamesGeneration;
}

void M_Code_Project::SetNotes( const wxString& notes ) {
  if( m_Notes != notes ) {
    m_Notes = notes;
    SetModified( true );
  }
}

const wxString& M_Code_Project::GetNotes() const {
  return m_Notes;
}

void M_Code_Project::SetShowNotesOnLoad( bool show ) {
  if( m_AutoShowNotesOnLoad != show ) {
    m_AutoShowNotesOnLoad = show;
    SetModified( true );
  }
}

bool M_Code_Project::GetShowNotesOnLoad() const {
  return m_AutoShowNotesOnLoad;
}

void M_Code_Project::SetCheckForExternallyModifiedFiles( bool check ) {
  if( m_CheckForExternallyModifiedFiles != check ) {
    m_CheckForExternallyModifiedFiles = check;
    SetModified( true );
  }
}

bool M_Code_Project::GetCheckForExternallyModifiedFiles() const {
  return m_CheckForExternallyModifiedFiles;
}

void M_Code_Project::ShowNotes( bool nonEmptyOnly, bool editable ) {
  if( !editable && nonEmptyOnly && m_Notes.IsEmpty() ) {
    return;
  }
  GenericMultiLineNotesDlg dlg( Manager::Get()->GetAppWindow(), _T( "Notes about " ) + m_Title, m_Notes, !editable );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    if( editable ) {
      SetNotes( dlg.GetNotes() );
    }
  }
}

void M_Code_Project::SetTitle( const wxString& title ) {
  if( title != GetTitle() ) {
    CompileTargetBase::SetTitle( title );
    NotifyPlugins( cbEVT_PROJECT_RENAMED );
  }
}

TiXmlNode* M_Code_Project::GetExtensionsNode() {
  if( !m_pExtensionsElement ) {
    m_pExtensionsElement = new TiXmlElement( cbU2C( _T( "Extensions" ) ) );
  }
  return m_pExtensionsElement;
}

void M_Code_Project::AddToExtensions( const wxString& stringDesc ) {
  TiXmlElement* elem = GetExtensionsNode()->ToElement();
  size_t pos = 0;
  while( true ) {
    while( pos < stringDesc.Length() && stringDesc.GetChar( pos ) == _T( '/' ) ) {
      ++pos;
    }
    size_t nextPos = pos;
    while( nextPos < stringDesc.Length() && stringDesc.GetChar( ++nextPos ) != _T( '/' ) && stringDesc.GetChar( nextPos ) != _T( ':' ) )
      ;
    wxString current = stringDesc.Mid( pos, nextPos - pos );
    if( current.IsEmpty() || current[0] == _T( ':' ) ) {
      break;
    }
    bool forceAdd = current[0] == _T( '+' );
    if( forceAdd ) {
      current.Remove( 0, 1 );
    }
    TiXmlElement* sub = !forceAdd ? elem->FirstChildElement( cbU2C( current ) ) : nullptr;
    if( !sub ) {
      sub = elem->InsertEndChild( TiXmlElement( cbU2C( current ) ) )->ToElement();
      SetModified( true );
    }
    elem = sub;
    if( stringDesc.GetChar( nextPos ) == _T( ':' ) ) {
      pos = nextPos + 1;
      nextPos = pos;
      while( nextPos < stringDesc.Length() && stringDesc.GetChar( ++nextPos ) != _T( '=' ) )
        ;
      if( pos == nextPos || nextPos == stringDesc.Length() ) {
      } else {
        wxString key = stringDesc.Mid( pos, nextPos - pos );
        wxString val = stringDesc.Mid( nextPos + 1, stringDesc.Length() - nextPos - 1 );
        sub->SetAttribute( cbU2C( key ), cbU2C( val ) );
        SetModified( true );
      }
      break;
    }
    pos = nextPos;
  }
}

void M_Code_Project::ProjectFileRenamed( ProjectFile* pf ) {
  for( ProjectFiles::iterator it = m_ProjectFilesMap.begin(); it != m_ProjectFilesMap.end(); ++it ) {
    ProjectFile* itpf = it->second;
    if( itpf == pf ) {
      m_ProjectFilesMap.erase( it );
      m_ProjectFilesMap[UnixFilename( pf->relativeFilename )] = pf;
      break;
    }
  }
}

void M_Code_Project::SetGlobs( const std::vector<Glob>& globs ) {
  m_Globs = globs;
}

std::vector<M_Code_Project::Glob> M_Code_Project::GetGlobs() const {
  return m_Globs;
}

wxString cbGetDynamicLinkerPathForTarget( M_Code_Project *project, ProjectBuildTarget* target ) {
  if( !target ) {
    return wxEmptyString;
  }
  Compiler* compiler = CompilerFactory::GetCompiler( target->GetCompilerID() );
  if( compiler ) {
    CompilerCommandGenerator* generator = compiler->GetCommandGenerator( project );
    wxString libPath;
    const wxString libPathSep =  _T( ":" );
    libPath << _T( "." ) << libPathSep;
    libPath << GetStringFromArray( generator->GetLinkerSearchDirs( target ), libPathSep );
    if( !libPath.IsEmpty() && libPath.Mid( libPath.Length() - 1, 1 ) == libPathSep ) {
      libPath.Truncate( libPath.Length() - 1 );
    }
    delete generator;
    return libPath;
  }
  return wxEmptyString;
}

wxString cbMergeLibPaths( const wxString &oldPath, const wxString &newPath ) {
  wxString result = newPath;
  const wxString libPathSep =  _T( ":" );
  if( !newPath.IsEmpty() && newPath.Mid( newPath.Length() - 1, 1 ) != libPathSep ) {
    result << libPathSep;
  }
  result << oldPath;
  return result;
}
